package com.ecr.hub.database.dao.impl;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import com.ecr.hub.database.DaoConcurrentAccessException;
import com.ecr.hub.database.dao.IDaoHub;
import com.ecr.hub.database.mapper.MapperHub;
import com.ecr.hub.model.composition.Composition;
import com.ecr.hub.model.enumeration.HubWagonTrailer;
import com.ecr.hub.model.rail.Rail;
import com.ecr.hub.model.sillon.Sillon;
import com.ecr.hub.model.wagon.Wagon;
import com.ecr.hub.model.wagon.WagonStatus;
import com.ecr.hub.model.wagon.WagonType;

@Repository("daoHub")
public class DaoHubImpl extends MapperHub implements IDaoHub {

	// Cette variable contient pour l'ensemble des utilisateurs le dernier lot à
	// récupérer pour lire la table hub_traffic. Le dao est monté en singleton
	// par Spring au démarrage de l'application.
	private int numLot = 0;

	// Récupération du dernier numéro de lot pour la table traffic (utilisé lors
	// de la connexion coordinateur).
	private static final String REQ_SELECT_MAX_LOT = "SELECT MAX(num_lot) FROM hub_traffic";

	// Récupération du trafic sur le hub pour affichage des wagons sur
	// l'ensemble des voies. Le tcms_train_id est copié dans la table
	// hub_traffic, car on à déjà 4 liaisons, il est inutile d'en rajouter 2
	// autres (compo/sillon) juste pour retrouver cette information.
	private static final String REQ_SELECT_TRAFFIC = "SELECT traffic.wagon_id, traffic.pos_rail_id, traffic.pos_num, traffic.fuel_level, traffic.sillon_ext_id, "
			+ "traffic.pos_real, traffic.pos_from_rail, traffic.status_id, traffic.destination_id, traffic.lading, traffic.ate, traffic.sillon_id, "
			+ "traffic.weight_lading, traffic.comment, traffic.brake_force, destination.hex_color as destination_hex_color, destination.label "
			+ "as destination_label, status.label as status_label, wagon.ext_id, wagon_type.id as type_id, wagon_type.label as type_label, wagon_type.trailer, "
			+ "wagon.owner, traffic.tcms_train_id, wagon_type.label as type_label, wagon_type.curb_weight, wagon_type.total_length, wagon.riv "
			+ "FROM hub_traffic as traffic, hub_wagon as wagon, hub_wagon_status as status, hub_destination as destination, "
			+ "hub_wagon_type as wagon_type WHERE traffic.wagon_id=wagon.id AND traffic.status_id=status.id AND traffic.destination_id=destination.id "
			+ "AND wagon.type_id=wagon_type.id AND traffic.num_lot=?";

	// Récupération du trafic sur le hub pour affichage des wagons sur
	// l'ensemble des voies. C'est cette requête qui est utilisée pour
	// l'affichage du hub.
	private static final String REQ_SELECT_TRAFFIC_ALL = REQ_SELECT_TRAFFIC
			+ " ORDER BY traffic.pos_rail_id, traffic.pos_num ASC";

	// Récupération de l'ensemble des wagons dans la table hub_traffic pour une
	// voie donnée.
	private static final String REQ_SELECT_TRAFFIC_RAIL = REQ_SELECT_TRAFFIC
			+ " AND traffic.pos_rail_id=? " + "ORDER BY traffic.pos_num ASC";

	// Récupération du relevé de composition à l'arrivée ou au départ du train
	// sur le Hub.
	private static final String REQ_SELECT_PLAN = "SELECT compo.id, compo.wagon_id, compo.lading, compo.ate, compo.brake_force, "
			+ "compo.weight_lading, compo.pos_num, compo.pos_from_rail,  compo.destination_ext_id, compo.pos_rail_save_id, "
			+ "compo.sillon_id, compo.pos_rail_id, compo.destination_id, compo.num_lot, sillon.tcms_train_id, sillon.ext_id as sillon_ext_id, "
			+ "compo.comment, wagon.ext_id as wagon_ext_id, rail.destination_hex_color, rail.destination_label, rail.bundle_type_id, "
			+ "rail.number, rail.destination_hex_color, wagon_type.total_length, wagon_type.curb_weight, wagon_type.label as type_label, "
			+ "destination.label as wagon_destination_label FROM hub_compo AS compo, hub_wagon as wagon, hub_rail as rail, hub_sillon as sillon, "
			+ "hub_wagon_type as wagon_type, hub_destination as destination WHERE compo.wagon_id=wagon.id AND compo.pos_rail_id=rail.id "
			+ "AND compo.sillon_id=sillon.id AND wagon.type_id=wagon_type.id AND destination.id=compo.destination_id AND compo.sillon_id=? "
			+ "ORDER BY compo.pos_num ASC";

	// Mise à jour de la position d'un wagon dans une rame (composition) à
	// partir de l'id ligne.
	private static final String REQ_UPDATE_PLAN_POS_NUM = "UPDATE hub_compo SET pos_num=? WHERE id=?";

	// Mise à jour de l'id voie et du commentaire dans la composition à partir
	// de l'id ligne.
	private static final String REQ_UPDATE_PLAN = "UPDATE hub_compo SET pos_rail_id=?, comment=? WHERE id=?";

	// Récupération de l'ensemble des statuts pour un wagon.
	private static final String REQ_SELECT_WAGON_STATUS = "SELECT * FROM hub_wagon_status WHERE trailer=?";

	// Récupération de l'ensemble des types pour un wagon.
	private static final String REQ_SELECT_WAGON_TYPE = "SELECT * FROM hub_wagon_type WHERE trailer=?";

	// Récupération de l'ensemble des wagons d'un train (un sillon lié à une
	// composition) dans le traffic.
	private static final String REQ_SELECT_WAGON_COMPOSITION = REQ_SELECT_TRAFFIC
			+ " AND sillon_id=?";

	// Insertion d'une ligne dans la table hub_traffic (une ligne = un wagon).
	private static final String REQ_INSERT_WAGON_TRAFFIC = "INSERT INTO hub_traffic (user_id, num_lot, sillon_id, "
			+ "lading, wagon_id, pos_from_rail, pos_rail_id, pos_num, pos_real, status_id, destination_id, "
			+ "sillon_ext_id, weight_lading, comment, tcms_train_id, fuel_level, ate, brake_force)"
			+ "VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)";

	// Recherche d'un wagon dans la table hub_wagon.
	private static final String REQ_SELECT_WAGON_BY_EXT_ID = "SELECT * FROM hub_wagon WHERE ext_id=?";

	// Insertion d'un nouveau wagon dans la table hub_wagon.
	private static final String REQ_INSERT_WAGON = "INSERT INTO hub_wagon (ext_id, type_id, owner, riv) VALUES (?,?,?,?)";

	// Récupération de l'identifiant pour le wagon qui a été inséré.
	private static final String REQ_SELECT_WAGON_LAST_ID = "SELECT LAST_INSERT_ID() FROM hub_wagon LIMIT 1";

	// Requête pour l'historisation de la table hub_traffic (1/2).
	private static final String REQ_HISTO_TRAFFIC_MOVE = "INSERT INTO hub_histo_traffic (SELECT * FROM hub_traffic WHERE num_lot < ?)";

	// Requête pour l'historisation de la table hub_traffic (2/2).
	private static final String REQ_HISTO_TRAFFIC_DEL = "DELETE FROM hub_traffic WHERE num_lot < ?";

	@Autowired
	JdbcTemplate jdbcTemplate;

	/**
	 * Récupération du traffic sur le hub. On récupère l'ensemble des wagons qui
	 * appartiennent au dernier lot. Un lot permet de regrouper l'ensemble des
	 * enregistrements qui doit être affiché à un instant donné.
	 */
	@Override
	public List<Wagon> getTraffic() throws DataAccessException {

		if (numLot == 0)
			numLot = jdbcTemplate.queryForInt(REQ_SELECT_MAX_LOT);

		return jdbcTemplate.query(REQ_SELECT_TRAFFIC_ALL,
				new Object[] { numLot }, mapperWagon);
	}

	/**
	 * Récupération du plan (travail / formation / composition) à partir de
	 * l'identifiant de sillon.
	 */
	@Override
	public List<Composition> getPlan(Sillon sillon) throws DataAccessException {

		return jdbcTemplate.query(REQ_SELECT_PLAN, mapperComposition,
				new Object[] { sillon.getId() });
	}

	/**
	 * Récupération de la liste des status pour un wagon. On liste l'ensemble de
	 * enregistrements de la table hub_wagon_status. Cette méthode est utilisée
	 * pour la formation des listes déroulantes.
	 */
	@Override
	public List<WagonStatus> getListWagonStatus(HubWagonTrailer trailer)
			throws DataAccessException {

		return jdbcTemplate.query(REQ_SELECT_WAGON_STATUS,
				new Object[] { trailer.getValue() }, mapperWagonStatus);
	}

	/**
	 * Récupération de l'ensemble des types pour un wagon (ou une locomotive).
	 * On liste l'ensemble des enregistrements pour la table hub_wagon_type.
	 * Cette méthode est utilisée pour la formation des listes déroulantes.
	 */
	@Override
	public List<WagonType> getListWagonTypes(HubWagonTrailer trailer)
			throws DataAccessException {

		return jdbcTemplate.query(REQ_SELECT_WAGON_TYPE,
				new Object[] { trailer.getValue() }, mapperWagonType);
	}

	/**
	 * Récupération de l'ensemble des wagons positionnés sur une voie donnée. On
	 * récupère à partir de la table hub_traffic (entre-autres), tous les wagons
	 * de la voie demandée. Cette méthode est surtout utilisée pour obtenir la
	 * photo de la voie à un instant t.
	 */
	@Override
	public List<Wagon> getListWagonsForRail(Rail rail)
			throws DataAccessException {

		List<Wagon> lstWagons = jdbcTemplate.query(REQ_SELECT_TRAFFIC_RAIL,
				mapperWagon, new Object[] { numLot, rail.getId() });

		if (lstWagons.size() == 0)
			throw new EmptyResultDataAccessException(0);
		return lstWagons;
	}

	/**
	 * Inversion du sens pour un train , on récupère l'ensemble des
	 * enregistrements pour un train (une composition) et on inverse le
	 * positionnement des wagons. Le dernier wagon passe en première position.
	 */
	@Override
	@Transactional
	public void updateReverseSense(Sillon sillon) throws DataAccessException {

		List<Composition> lstCompositions = jdbcTemplate.query(REQ_SELECT_PLAN,
				mapperComposition, new Object[] { sillon.getId() });
		int pos = lstCompositions.size() - 1;
		for (int i = 0; i < lstCompositions.size(); i++) {
			jdbcTemplate.update(REQ_UPDATE_PLAN_POS_NUM, new Object[] { pos,
					lstCompositions.get(i).getId() });
			pos--;
		}
	}

	/**
	 * Cette méthode est l'une des plus importantes, elle est chargée de la mise
	 * à jour du traffic dans le hub. On procède en deux temps. 1) On incrémente
	 * le numéro de lot qui devient le dernier lot à lire pour visualiser le
	 * hub. 2) on lit l'ensemble des voies, on récupère les wagons et on les
	 * enregistre. Ainsi , à chaque modification effectuée par le coordinateur,
	 * on "flush" la totalité de son écran dans la base de données.On en profite
	 * pour faire une vérification supplémentaire avant l'envoi en base de
	 * données.
	 */
	@Override
	@Transactional
	public void updateTraffic(List<Rail> lstRails) throws DataAccessException {
		numLot++;
		for (Rail rail : lstRails) {
			for (Wagon wagon : rail.getLstWagons()) {
				jdbcTemplate.update(
						REQ_INSERT_WAGON_TRAFFIC,
						new Object[] { 0, numLot, wagon.getSillonId(),
								wagon.getLading(), wagon.getId(),
								wagon.getFromRail(),
								wagon.getCurrentPos().getPosRailId(),
								wagon.getCurrentPos().getPosNum(),
								wagon.getCurrentPos().isPosReal(),
								wagon.getCurrentStatus().getId(),
								wagon.getDestinationId(),
								wagon.getSillonExtId(),
								wagon.getWeightLading(), wagon.getComment(),
								wagon.getTcmsTrainId(), wagon.getFuelLevel(),
								wagon.getAte(), wagon.getBrakeForce() });
			}
		}
	}

	/**
	 * Mise à jour des enregistrements de composition avec le railId et le
	 * commentaire. Il se peut que le coordinateur ait demandé des modifications
	 * de voie pour certains wagons. Les wagons ont été déplacés dans le hub
	 * (table hub_traffic) mais il faut reporter ces modifications dans la table
	 * hub_compo. On parcours la liste des compositions et on effectue la
	 * modification On ne se pose pas de question et on met à jour tous les
	 * wagons.
	 */
	@Override
	@Transactional
	public void updateWorkingPlan(List<Composition> lstCompositions)
			throws DataAccessException {

		for (Composition composition : lstCompositions) {
			{
				jdbcTemplate.update(REQ_UPDATE_PLAN, new Object[] {
						composition.getWagon().getNextPos().getPosRailId(),
						composition.getComment(), composition.getId() });
			}
		}
	}

	/**
	 * Récupération de l'ensemble des wagons liés à un train (un sillon) dans la
	 * table hub_traffic. cela permet de retrouver tousles emplacement (position
	 * dans la voie comprise) des wagons. Cette requête est utilisée pour les
	 * modifications de statut sur l'ensemble des wagons d'un train.
	 */
	@Override
	public List<Wagon> getListWagonTrafficComposition(Sillon sillon)
			throws DataAccessException {

		List<Wagon> lstWagons = jdbcTemplate.query(
				REQ_SELECT_WAGON_COMPOSITION,
				new Object[] { numLot, sillon.getId() }, mapperWagon);
		if (lstWagons.size() == 0)
			throw new EmptyResultDataAccessException(0);
		return lstWagons;
	}

	/**
	 * Récupération d'un wagon dans la table hub_wagon à partir de son
	 * identifiant externe. (Il peut soit s'agir d'un wagon, soit d'une
	 * locomotive).
	 */
	@Override
	public Wagon getWagonByExtId(String extId) throws DataAccessException {

		return jdbcTemplate.queryForObject(REQ_SELECT_WAGON_BY_EXT_ID,
				new Object[] { extId.toUpperCase() }, mapperWagonBase);
	}

	/**
	 * Insertion d'un nouveau wagon (ou locomotive car dépend du type) dans la
	 * table hub_wagon. Ici, il y a très peu de données à insérer, mais il faut
	 * récupérer l'identifiant interne du wagon.
	 */
	@Override
	public int insertWagon(Wagon wagon) throws DataAccessException {

		jdbcTemplate.update(REQ_INSERT_WAGON,
				new Object[] { wagon.getExtId(), wagon.getType().getId(),
						wagon.getOwner().toUpperCase(), wagon.getRiv() });

		return jdbcTemplate.queryForInt(REQ_SELECT_WAGON_LAST_ID);
	}

	/**
	 * Historisation de la table traffic, on transfert l'ensemble des
	 * enregistrements dont le numéro de lot est inférieur au dernier numéro,
	 * puis on supprime les données de la table hub_traffic. Comme ici on peut
	 * avoir de nombreux enregistrements, on ne passe pas par une boucle.
	 * 
	 * La problématique est la suivante : que se passe t'il si des lignes sont
	 * ajoutées dans la table entre les deux requêtes (déplacement et
	 * suppression). Vu le peu de chance que cela arrive avec l'utilisation de
	 * l'application par une seule personne et afin d'éviter de monter une
	 * solution complexe avec des tables temporaires, on fait juste un contrôle
	 * du nombre de ligne. En cas de différences, on reporte la traitement au
	 * prochain passage.
	 */
	@Override
	@Transactional
	public int historizeTraffic() throws DataAccessException {

		if (numLot == 0)
			numLot = jdbcTemplate.queryForInt(REQ_SELECT_MAX_LOT);

		int nbRecords = jdbcTemplate.update(REQ_HISTO_TRAFFIC_MOVE,
				new Object[] { numLot });

		int nbDelRecords = jdbcTemplate.update(REQ_HISTO_TRAFFIC_DEL,
				new Object[] { numLot });

		if (nbDelRecords != nbRecords)
			throw new DaoConcurrentAccessException(
					"Insertion de données lors de la purge de la table hub_traffic.");

		return nbRecords;
	}
}
